package event

import (
	"bytes"
	"reflect"
	"runtime"
	"strings"
	"sync"
)

const Wildcard = "*"

type HandlerFunc func(d *Data) error

type Manager struct {
	pool     sync.Pool
	names    map[string]int
	handlers map[string][]HandlerFunc
}

func NewManager() *Manager {
	em := &Manager{
		names:    make(map[string]int),
		handlers: make(map[string][]HandlerFunc),
	}

	em.pool.New = func() interface{} {
		return &Data{}
	}

	return em
}

func (em *Manager) On(name string, handler HandlerFunc) {
	name = strings.TrimSpace(name)
	if name == "" {
		panic("event name cannot be empty")
	}

	if ls, ok := em.handlers[name]; ok {
		em.names[name]++
		em.handlers[name] = append(ls, handler)
	} else {
		em.names[name] = 1
		em.handlers[name] = []HandlerFunc{handler}
	}
}

func (em *Manager) MustFire(name string, args ...interface{}) {
	err := em.Fire(name, args...)
	if err != nil {
		panic(err)
	}
}

func (em *Manager) Fire(name string, args ...interface{}) (err error) {
	handlers, ok := em.handlers[name]
	if !ok {
		return
	}

	e := em.pool.Get().(*Data)
	e.init(name, args)

	err = em.doFire(e, handlers)

	e.reset()
	em.pool.Put(e)
	return err
}

func (em *Manager) doFire(d *Data, handlers []HandlerFunc) (err error) {
	err = em.callHandler(d, handlers)
	if err != nil || d.IsAborted() {
		return
	}

	if em.HashEvent(Wildcard) {
		err = em.callHandler(d, em.handlers[Wildcard])
	}

	return
}

func (em *Manager) callHandler(d *Data, handlers []HandlerFunc) (err error) {
	for _, handler := range handlers {
		err = handler(d)
		if err != nil || d.IsAborted() {
			return
		}
	}
	return
}

func (em *Manager) HashEvent(name string) bool {
	_, ok := em.names[name]
	return ok
}

func (em *Manager) GetEventHandlers(name string) (es []HandlerFunc) {
	es, _ = em.handlers[name]
	return
}

func (em *Manager) EventHandlers() map[string][]HandlerFunc {
	return em.handlers
}

func (em *Manager) EventNames() map[string]int {
	return em.names
}

func (em *Manager) String() string {
	buf := new(bytes.Buffer)
	for name, hs := range em.handlers {
		buf.WriteString(name + " handlers:\n ")
		for _, h := range hs {
			buf.WriteString(funcName(h))
		}
		buf.WriteString("\n")
	}
	return buf.String()
}

func (em *Manager) ClearHandlers(name string) bool {
	_, ok := em.handlers[name]
	if ok {
		delete(em.names, name)
		delete(em.handlers, name)
	}
	return ok
}

func (em *Manager) Clear() {
	em.names = map[string]int{}
	em.handlers = map[string][]HandlerFunc{}
}

func funcName(f interface{}) string {
	return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
}
